﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Windows.Forms;
using PMC;
using PMC.Interfaces;
using PMC.Scripts;
using PMC.Structs;
using PMC.Urls;

namespace PMCgui
{
    public partial class WebbrowserControl : UserControl
    {
        private IWbrowser browser;

        private readonly string statisticsFormat = "Url: {0}" + Environment.NewLine + "Script: {1}" +
                                                   Environment.NewLine +
                                                   "Time left: {2}";

        private const string deactivatedString = "This browser has been deactivated";
        private const string inactiveString = "";
        private Timer updateTimer = new Timer();
        private DateTime newVisited;
        private Website website;
        private Uri url;

        public WebbrowserControl(IWbrowser browser)
        {
            InitializeComponent();
            lbInformation.Text = inactiveString;
            Browser = browser;
            browser.BrowserStateChanged += browser_BrowserStateChanged;
            updateTimer.Tick += updateTimer_Tick;
            updateTimer.Interval = 500;
            browser.StartAsync();
        }

        void browser_BrowserStateChanged(BrowserState newState)
        {
            if (InvokeRequired)
            {
                Invoke((MethodInvoker)(() => browser_BrowserStateChanged(newState)));
                return;
            }

            if (newState == BrowserState.Stopped)
            {
                updateTimer.Stop();
                lbInformation.Text = deactivatedString;
            }
            else
            {
                updateTimer.Start();
            }
        }

        void updateTimer_Tick(object sender, EventArgs e)
        {
            Redraw(website, url);
        }

        public IWbrowser Browser
        {
            get
            {
                return browser;
            }
            set
            {
                var control = value as Control ?? DynamicCast<Control>(value);
                pnlBrowser.Controls.Clear();
                pnlBrowser.Controls.Add(control);
                control.Dock = DockStyle.Fill;
                value.WebpageVisited += value_WebpageVisited;
                Invalidate();
                browser = value;
            }
        }

        private void value_WebpageVisited(PMC.Structs.Website website, Uri visited)
        {
            this.website = website;
            url = visited;
            newVisited = DateTime.Now;
        }

        private void Redraw(Website website, Uri visited)
        {
            var script = website != null ? website.Script : Script.Default;
            var seconds = Math.Ceiling((newVisited + TimeSpan.FromMilliseconds(script.Interval) - DateTime.Now).TotalSeconds);
            seconds = seconds > 0 ? seconds : 0;
            string timer = Convert.ToString(seconds);//((double)value.TimerInterval / 1000).ToString();
            lbInformation.Text = string.Format(statisticsFormat, Convert.ToString(visited), script, timer);
        }

        /// <summary>
        /// Dynamicly casts to the right object
        /// </summary>
        /// <see cref="http://codegoeshere.blogspot.nl/2007/05/dynamic-cast-in-c.html"/>
        /// <typeparam name="T">Type to cast the object to</typeparam>
        /// <param name="o">Object to cast</param>
        /// <returns>Casted object</returns>
        /// <exception cref="InvalidCastException">If the object cannot be cast to T (through Reflection) an exception will be thrown</exception>
        private T DynamicCast<T>(object o)
        {
            Type ot = o.GetType();
            string[] methodNames = new[] { "op_Implicit", "op_Explicit" };
            MethodInfo meth = Array.Find(
                    ot.GetMethods(BindingFlags.Static | BindingFlags.Public),
                    inf => (methodNames.Contains(inf.Name) && inf.ReturnType == typeof(T)));

            if (meth == null) throw new InvalidCastException("Invalid Cast.");

            return (T)meth.Invoke(null, new[] { o });
        }
    }
}
